#include "CameraNode.h"
#include "IRenderer.h"
#include "OnWindowResizeEvent.h"

CameraNode::CameraNode( Subject<Maths::Vector3D>* position, Subject<Maths::Vector3D>* rotation )
{
	m_position = new ValueTrigger<Maths::Vector3D>(position);
	m_orientation = new ValueTrigger<Maths::Vector3D>(rotation);
}


HashedString CameraNode::Type() const
{
	return HashedString("CameraNode");
}

void CameraNode::Enable()
{
	Renderer->PushProjectionMatrix(getProjectionMatrix());
	Renderer->PushModelViewMatrix(getViewMatrix());
	INode::Enable();
	Disable();
}

void CameraNode::Disable()
{
	Renderer->PopProjectionMatrix();
	Renderer->PopModelViewMatrix();
}

Maths::Matrix CameraNode::getProjectionMatrix()
{
	Maths::Matrix projection(m_fovy, m_aspect, m_near, m_far);
	return projection;
}

Maths::Matrix CameraNode::getViewMatrix()
{
	Maths::Vector3D& position = ((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_position)->getObject();
	Maths::Vector3D& orientation = ((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject();
	Maths::Quaternion rotX(orientation.X(), 0, 0);
	Maths::Quaternion rotY(0, orientation.Y(), 0);
	Maths::Quaternion rotZ(0, 0, orientation.Z());

	Maths::Quaternion rotation = rotY * rotX * rotZ;

	Maths::Matrix view;
	view = m_postRotation.GetMatrix() * view;
	view.Translate(-position.X(), -position.Y(), -position.Z());

	view = rotation.GetMatrix() * view;

	return view;
}

void CameraNode::setProjection()
{
	m_projection = true;
}

bool CameraNode::IsProjection()
{
	return m_projection;
}

void CameraNode::setOrtho()
{
	m_projection = false;
}

float& CameraNode::Near()
{
	return m_near;
}

float& CameraNode::Far()
{
	return m_far;
}

float& CameraNode::Aspect()
{
	return m_aspect;
}

float& CameraNode::Fovy()
{
	return m_fovy;
}

float& CameraNode::Left()
{
	return m_left;
}

float& CameraNode::Right()
{
	return m_right;
}

float& CameraNode::Top()
{
	return m_top;
}

float& CameraNode::Bottom()
{
	return m_bottom;
}

Maths::Vector3D& CameraNode::Position()
{
	return ((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_position)->getObject();
}

Maths::Vector3D& CameraNode::Rotation()
{
	return ((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject();
}

void CameraNode::setPosition( Subject<Maths::Vector3D>& subject )
{
	m_position = new ValueTrigger<Maths::Vector3D>(&subject);
}

void CameraNode::setPosition( Maths::Vector3D& position )
{
	TriggerBase* base = m_position;
	ValueTrigger<Maths::Vector3D>* trigger = (ValueTrigger<Maths::Vector3D>*)base;
	*trigger = position;
}

void CameraNode::setRotation( Subject<Maths::Vector3D>& subject )
{
	m_orientation = new ValueTrigger<Maths::Vector3D>(&subject);	
}

void CameraNode::setRotation( Maths::Vector3D& angles )
{
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject() = angles;
}

void CameraNode::setRotationX( float angle )
{
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject().X() = angle;
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject() = 
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject();
}

void CameraNode::setRotationY( float angle )
{
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject().Z() = angle;
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject() = 
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject();
}

void CameraNode::setRotationZ( float angle )
{
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject().Z() = angle;
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject() = 
	((ValueTrigger<Maths::Vector3D>*)(TriggerBase*)m_orientation)->getObject();
}

ValueTrigger<Maths::Vector3D>& CameraNode::getPosition( )
{
	return *static_cast<ValueTrigger<Maths::Vector3D>*>(m_position.getPointer());
}

ValueTrigger<Maths::Vector3D>& CameraNode::getRotation( )
{
	return *static_cast<ValueTrigger<Maths::Vector3D>*>(m_orientation.getPointer());
}

bool CameraNode::OnResize( IEvent& event )
{
	OnWindowResizeEvent& resize = static_cast<OnWindowResizeEvent&>(event);
	m_aspect = (float)resize.Size().X / (float)resize.Size().Y;
	return false;
}

void CameraNode::Unproject( float x, float y, Maths::Vector3D& start, Maths::Vector3D& end )
{
	float w;
	Maths::Vector3D coords;
	Maths::Matrix p = getViewMatrix() * getProjectionMatrix();
	//Maths::Matrix p = getProjectionMatrix() * getViewMatrix();
	Maths::Matrix final = p.getInvert();
	
    // Map x and y from window coordinates 
    coords.X() = (x / (float)Renderer->getWindowWidth());
    coords.Y() = ((float)Renderer->getWindowHeight() - y) / (float)Renderer->getWindowHeight();
    // Map to range -1 to 1 
    coords.X() = coords.X() * 2.0f - 1.0f;
    coords.Y() = coords.Y() * 2.0f - 1.0f;
    coords.Z() = 1.0f;

	end = final.mvMulti(coords, &w);
	end /= w;
	
	coords.Z() = 0.0f;

	start = final.mvMulti(coords, &w);
	start /= w;
}
void CameraNode::setPostRotation( Maths::Quaternion& postrotation )
{
	m_postRotation = postrotation;
}

Maths::Quaternion CameraNode::getPostRotation()
{
	return m_postRotation;
}